package hn.sigit.view.viewer;

import hn.sigit.dao.hnd.cadastre.HND_SpatialZoneDAO;
import hn.sigit.model.commons.IRRR;
import hn.sigit.model.commons.IRestriction;
import hn.sigit.model.commons.IRight;
import hn.sigit.model.commons.ISpatialZone;
import hn.sigit.model.hnd.administrative.HND_LegalPerson;
import hn.sigit.model.hnd.administrative.HND_NaturalPerson;
import hn.sigit.model.hnd.cadastre.HND_Parcel;
import hn.sigit.model.hnd.cadastre.HND_Property;
import hn.sigit.model.hnd.cadastre.HND_SpatialZone;
import hn.sigit.model.ladm.administrative.LA_Right;
import hn.sigit.model.ladm.external.ExtParty;
import hn.sigit.view.viewer.dbf.DBFFieldNames;
import hn.sigit.view.viewer.dbf.PartyDBF;
import hn.sigit.view.viewer.dbf.PropertyDBF;
import hn.sigit.view.viewer.dbf.Property_SpatialZoneDBF;
import hn.sigit.view.viewer.dbf.ResponsibilitiesDBF;
import hn.sigit.view.viewer.dbf.RestrictionsDBF;
import hn.sigit.view.viewer.dbf.RightsDBF;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;

import org.geotools.data.DefaultTransaction;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

import com.vividsolutions.jts.geom.Geometry;


public class InteractiveViewerLayerHelper implements Serializable {
	private static final long serialVersionUID = -4875408467949573597L;
	
	private static final String DBF_EXT = ".DBF";
	
	private static final String SPATIALZONE = "SPATIALZONE";
	private static final String PARCEL = "PARCEL";
	private static final String PROPERTY_SPATIALZONE = "PROPERTY_SPATIALZONE";
	private static final String PROPERTY = "PROPERTY";
	private static final String RIGHTS = "RIGHTS";
	private static final String RESTRICTIONS = "RESTRICTIONS";
	private static final String RESPONSIBILITIES = "RESPONSIBILITIES";
	private static final String PARTY = "PARTY";
	
	private static final String[] FILE_NAMES = { SPATIALZONE,
		PARCEL, PROPERTY_SPATIALZONE, PROPERTY, RIGHTS,
		RESTRICTIONS, RESPONSIBILITIES, PARTY
	};
	
	private InteractiveViewerHelper interactiveViewerHelper;

	public InteractiveViewerLayerHelper(InteractiveViewerHelper ivh) {
		interactiveViewerHelper = ivh;
	}

	private String getOriginalDBFFileName(String fileName) {
		for (String fname : FILE_NAMES) {
			if (fileName.startsWith(fname)) {
				int idx = fileName.lastIndexOf('.');
				if (idx >= 0) {
					String ext = fileName.substring(idx);
					return fname + ext;
				}
				else
					return fname;
			}
		}
		return fileName;
	}
	
	private List<File> generateFiles(List<HND_SpatialZone> spatialZoneList)
	throws IOException, Exception {
		Property_SpatialZoneDBF propSzDBF = new Property_SpatialZoneDBF(
				File.createTempFile(PROPERTY_SPATIALZONE, DBF_EXT));
		propSzDBF.open();
		
		PropertyDBF propDBF = new PropertyDBF(
				File.createTempFile(PROPERTY, DBF_EXT));
		propDBF.open();
		
		RightsDBF rightsDBF = new RightsDBF(
				File.createTempFile(RIGHTS, DBF_EXT));
		rightsDBF.open();
		
		RestrictionsDBF restrictionsDBF = new RestrictionsDBF(
				File.createTempFile(RESTRICTIONS, DBF_EXT));
		restrictionsDBF.open();
		
		ResponsibilitiesDBF responsibilitiesDBF = new ResponsibilitiesDBF(
				File.createTempFile(RESPONSIBILITIES, DBF_EXT));
		responsibilitiesDBF.open();
		
		PartyDBF partyDBF = new PartyDBF(
				File.createTempFile(PARTY, DBF_EXT));
		partyDBF.open();

		for (HND_SpatialZone spatialZone : spatialZoneList) {
			HND_Property property = spatialZone.getProperty();
			if (property != null) {
				propSzDBF.addRecord(spatialZone.getSuID(), property.getuID());
				propDBF.addRecord(property.getuID(), property.getRegistration(),
						property.getTome(), property.getFolio(),
						property.getAnnotationNumber());
				
				for (IRRR rrr : property.getRrr()) {
					ExtParty extParty = rrr.getParty().getExtParty();
					
					if (rrr instanceof IRight)
						rightsDBF.addRecord(rrr.getRID(), property.getuID(),
								extParty.getExtPID(), rrr.getDescription(),
								rrr.getShare().getNumerator(), rrr.getShare().getDenominator(),
								rrr.getType().toString());
					else if (rrr instanceof IRestriction)
						restrictionsDBF.addRecord(rrr.getRID(), property.getuID(),
								extParty.getExtPID(), rrr.getDescription(),
								rrr.getShare().getNumerator(), rrr.getShare().getDenominator(),
								rrr.getType().toString(), ((IRestriction) rrr).getPartyRequired());
					else
						responsibilitiesDBF.addRecord(rrr.getRID(), property.getuID(),
								extParty.getExtPID(), rrr.getDescription(),
								rrr.getShare().getNumerator(), rrr.getShare().getDenominator(),
								rrr.getType().toString());
						
						
					if (extParty instanceof HND_NaturalPerson) {
						HND_NaturalPerson natPersonParty = (HND_NaturalPerson) extParty;
						partyDBF.addNaturalPerson(extParty.getExtPID(),
								natPersonParty.getIdentity(),
								natPersonParty.getRtn(),
								'N',
								natPersonParty.getName(),
								natPersonParty.getAddressDescription(),
								natPersonParty.getPhones(),
								natPersonParty.getEmails(),
								natPersonParty.getFirstName1(),
								natPersonParty.getFirstName2(),
								natPersonParty.getLastName1(),
								natPersonParty.getLastName2(),
								natPersonParty.getGender(),
								natPersonParty.getNationality(),
								natPersonParty.getDob(),
								natPersonParty.getMaritalStatus());
					}
					else if (extParty instanceof HND_LegalPerson) {
						HND_LegalPerson legalPersonParty = (HND_LegalPerson) extParty;
						partyDBF.addLegalPerson(extParty.getExtPID(),
								null,
								legalPersonParty.getRtn(),
								'N',
								legalPersonParty.getName(),
								legalPersonParty.getAddressDescription(),
								legalPersonParty.getPhones(),
								legalPersonParty.getEmails());
					}
				}
			}
		}
		
		propSzDBF.close();
		propDBF.close();
		rightsDBF.close();
		restrictionsDBF.close();
		responsibilitiesDBF.close();
		partyDBF.close();
		
		
		
		List<File> resultFiles = new ArrayList<File>();
		resultFiles.add(propSzDBF.getFile());
		resultFiles.add(propDBF.getFile());
		resultFiles.add(rightsDBF.getFile());
		resultFiles.add(restrictionsDBF.getFile());
		resultFiles.add(responsibilitiesDBF.getFile());
		resultFiles.add(partyDBF.getFile());
		
		return resultFiles;
	}
	
	public String downloadSelectedLayer() {
		Date date = new Date();
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		String dateText = String.format("%d.%02d.%02d.%02d.%02d.%02d.%04d",
				cal.get(Calendar.YEAR),
				cal.get(Calendar.MONTH) + 1,
				cal.get(Calendar.DAY_OF_MONTH),
				cal.get(Calendar.HOUR_OF_DAY),
				cal.get(Calendar.MINUTE),
				cal.get(Calendar.SECOND),
				cal.get(Calendar.MILLISECOND));

		try {
			List<HND_SpatialZone> spatialZoneList = HND_SpatialZoneDAO.loadSpatialZonesByLayer(interactiveViewerHelper.getSelectedLayer());
			List<File> filesToZip = generateShapeFiles(spatialZoneList);
			
			filesToZip.addAll(generateFiles(spatialZoneList));


			if (filesToZip != null && filesToZip.size() > 0) {
				FacesContext facesContext = FacesContext.getCurrentInstance();
				facesContext.responseComplete();
				
				ExternalContext externalContext = facesContext.getExternalContext();
				HttpServletResponse response = (HttpServletResponse) externalContext
						.getResponse();

				response.reset(); // Some JSF component library or some Filter might
									// have set some headers in the buffer
									// beforehand. We want to get rid of them, else
									// it may collide.
				response.setContentType("application/x-download");
				response.setHeader(
						"Content-disposition",
						String.format(
								"attachment; filename=\"sigit_data_%s.zip\"",
								dateText
						)
				);

				BufferedOutputStream output = null;

				try {
					output = new BufferedOutputStream(response.getOutputStream());
					writeZipFile(filesToZip, output);
					
					for (File file : filesToZip)
						file.delete();
					
				} finally {
					if (null != output)
						output.close();
				}

			}
		}
		catch (FileNotFoundException fnfe) {
			fnfe.printStackTrace();
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR, "oops", "oops"));
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR, "oops", "oops"));
		}
		catch (Exception ex) {
			ex.printStackTrace();
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR, "oops", "oops"));
		}

		return null;
	}
	
	private void writeZipFile(final List<File> inputFiles, OutputStream outStream) {
		final int BUFFER = 4192;

		try {
			ZipOutputStream out = new ZipOutputStream(outStream);
			BufferedInputStream origin = null;
			byte data[] = new byte[BUFFER];
			for (File file : inputFiles) {
				if (file.getName().endsWith(".zip")) continue;
				
				try {
					FileInputStream fi = new FileInputStream(file);
					origin = new BufferedInputStream(fi, BUFFER);
					
					ZipEntry entry = new ZipEntry(getOriginalDBFFileName(file.getName()));
					out.putNextEntry(entry);

					int count;
					while ((count = origin.read(data, 0, BUFFER)) != -1)
						out.write(data, 0, count);
					origin.close();
				} catch (Exception ex) {
					ex.printStackTrace();
				}
			}
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static SimpleFeatureType createSpatialZoneFeatureType(HND_SpatialZone spatialZone) {
		Class<? extends Geometry> clazz = spatialZone.getShape().getClass();
		SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
		builder.setName("Location");
		builder.setCRS(DefaultGeographicCRS.WGS84);
		
		//Defines the arributes for a Parcel
		//Attributes must be set in this order
		//TODO: Know what to do when just a spatial zone
		
		builder.add(DBFFieldNames.SPATIAL_ZONE.ZoneId, Long.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.Shape, clazz);
		builder.add(DBFFieldNames.SPATIAL_ZONE.ZoneName, String.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.LocationInCountry, String.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.GeometryPerimeter, Double.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.MeasuredPerimeter, Double.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.DocumentedPerimeter, Double.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.GeometryArea, Double.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.MeasuredArea, Double.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.DocumentedArea, Double.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.ProposedLandUse, String.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.CurrentLandUse, String.class);
		builder.add(DBFFieldNames.SPATIAL_ZONE.GeomHash, String.class);
		
		//C'est la part du Parcel
		if (spatialZone instanceof HND_Parcel) {
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.FieldTab, Long.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.MunicipalKey, String.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.CadastralKey, String.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.NeighborhoodName, String.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.AccessWay1, String.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.AccessWay2, String.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.HouseNumber, String.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.DocumentedBuiltArea, Double.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.GroundBuiltArea, Double.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.CommercialAppraisal, BigDecimal.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.FiscalAppraisal, BigDecimal.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.PayedTaxes, BigDecimal.class);
			builder.add(DBFFieldNames.SPATIAL_ZONE.PARCEL.Status, String.class);
		}
		// build the type
		final SimpleFeatureType ZONE = builder.buildFeatureType();

		return ZONE;
	}
	
	private String spatialZoneHash(ISpatialZone spatialZone) {
		return "" + spatialZone.hashCode();
	}
	private String spatialZoneGeomHash(ISpatialZone spatialZone) {
		return "" + spatialZone.getShape().hashCode();
	}
	private String nullToString(Object obj) {
		if (obj != null)
			return obj.toString();
		return "";
	}

	private List<File> generateShapeFiles(List<HND_SpatialZone> spatialZoneList) {
		List<File> resultFiles = null;

		SimpleFeatureCollection collection = FeatureCollections.newCollection();
		SimpleFeatureType TYPE = null;
		SimpleFeatureBuilder featureBuilder = null;

		
		for (HND_SpatialZone spatialZone : spatialZoneList) {
			if (TYPE == null)
				TYPE = createSpatialZoneFeatureType(spatialZone);
			if (featureBuilder == null)
				featureBuilder = new SimpleFeatureBuilder(TYPE);
			
			featureBuilder.add(spatialZone.getSuID());
			featureBuilder.add(spatialZone.getShape());
			featureBuilder.add(spatialZone.getZoneName());
			featureBuilder.add(spatialZone.getLocationInCountry());
			featureBuilder.add(spatialZone.getGeometryPerimeter());
			featureBuilder.add(spatialZone.getMeasuredPerimeter());
			featureBuilder.add(spatialZone.getDocumentedPerimeter());
			featureBuilder.add(spatialZone.getGeometryArea());
			featureBuilder.add(spatialZone.getMeasuredArea());
			featureBuilder.add(spatialZone.getDocumentedArea());
			featureBuilder.add(spatialZone.getProposedLandUse() != null ? spatialZone.getProposedLandUse().getCompleteCode() : "");
			featureBuilder.add(spatialZone.getCurrentLandUse() != null ? spatialZone.getCurrentLandUse().getCompleteCode() : "");
			featureBuilder.add(spatialZoneGeomHash(spatialZone));
			
			if (spatialZone instanceof HND_Parcel) {
				HND_Parcel parcel = (HND_Parcel) spatialZone;
				
				featureBuilder.add(parcel.getFieldTab());
				featureBuilder.add(parcel.getMunicipalKey());
				featureBuilder.add(parcel.getCadastralKey());
				featureBuilder.add(parcel.getNeighborhood());
				featureBuilder.add(parcel.getAccessWay1());
				featureBuilder.add(parcel.getAccessWay2());
				featureBuilder.add(parcel.getHouseNumber());
				featureBuilder.add(parcel.getDocumentedBuiltArea());
				featureBuilder.add(parcel.getGroundBuiltArea());
				featureBuilder.add(parcel.getCommercialAppraisal());
				featureBuilder.add(parcel.getFiscalAppraisal());
				featureBuilder.add(parcel.getPayedTaxes());
				featureBuilder.add(nullToString(parcel.getStatus()));
			}

			SimpleFeature feature = featureBuilder.buildFeature(null);
			collection.add(feature);
		}
		
		try {
			//Creates a new file in the temporary-file directory
			final File newFile = File.createTempFile(PARCEL, ".shp");

			ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();

			Map<String, Serializable> params = new HashMap<String, Serializable>();
			try {
				params.put("url", newFile.toURI().toURL());
				params.put("create spatial index", Boolean.FALSE);
			} catch (MalformedURLException ex) {
				System.err.println("URL mal formado: " + ex.getMessage());
			}

			ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory
					.createNewDataStore(params);
			newDataStore.createSchema(TYPE);

			Transaction transaction = new DefaultTransaction("create");

			String typeName = newDataStore.getTypeNames()[0];
			SimpleFeatureSource featureSource = newDataStore
					.getFeatureSource(typeName);

			if (featureSource instanceof SimpleFeatureStore) {
				SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;

				featureStore.setTransaction(transaction);
				try {
					featureStore.addFeatures(collection);
					transaction.commit();
					
					File dir = new File(newFile.getParent());
					File[] resultFilesArray = dir.listFiles(new FilenameFilter() {
						@Override
						public boolean accept(File dir, String name) {
							String shpName = newFile.getName();
							return name.startsWith(shpName.substring(0, shpName
									.length() - 4));
						}
						
					});
					resultFiles = new ArrayList<File>();
					
					//TODO: corregir lo de los nombres!
					for (File f : resultFilesArray)
						resultFiles.add(f);

				} catch (Exception problem) {
					problem.printStackTrace();
					transaction.rollback();

				} finally {
					transaction.close();
				}

			} else {
				System.out.println(typeName
						+ " does not support read/write access");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return resultFiles;
	}
	
	public void download() throws IOException {
		/*
        FacesContext facesContext = FacesContext.getCurrentInstance();
        File[] shapeFiles = null;
        try {
        	//Paso 1: Obtener lista de entidades que satisfagan el query espacial
        	String gid = transactionController.getGid();
        	List<Countries> geomList = findTouches(Integer.valueOf(gid).intValue());
        	
        	//Paso 2: Generar archivos shape de la geometria
        	shapeFiles = generateShapeFiles(geomList);
        	
        	//Paso 3: Escribir al stream de salida ZIP los archivos shape generados
			ExternalContext externalContext = facesContext.getExternalContext();
			HttpServletResponse response = (HttpServletResponse) externalContext
					.getResponse();

			response.reset(); // Some JSF component library or some Filter might
								// have set some headers in the buffer
								// beforehand. We want to get rid of them, else
								// it may collide.
			response.setContentType("application/x-download");
			response.setHeader("Content-disposition",
					"attachment; filename=\"shape.zip\"");

			BufferedOutputStream output = null;

			try {
				output = new BufferedOutputStream(response.getOutputStream());

				writeZipFile(shapeFiles, output);
			} finally {
				if (null != output)
					output.close();
			}

        }
        catch (Exception e) {
        	e.printStackTrace();
        }
        finally {
        	//marcar los archivos shape generados para ser eliminados
        	//al terminar la instancia de la Java VM
        	for (File f : shapeFiles)
        		f.deleteOnExit();
        	
			facesContext.responseComplete();
        }
        */
    }



}
